package org.song.config;

import org.song.filter.MyOncePerRequestFilter;
import org.song.filter.authentication.MyDynamicPermission;
import org.song.filter.certification.MyAuthenticationFailureHandler;
import org.song.filter.certification.MyAuthenticationSuccessHandler;
import org.song.filter.certification.MyUsernamePasswordAuthenticationFilter;
import org.song.util.BCryptPasswordEncoderUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;
import org.springframework.web.cors.CorsUtils;

/**
 * @author song
 * @date 2022/3/9 17:03
 * 自定义springSecurity配置
 **/

@Configuration
// @EnableWebSecurity  关闭springboot自动装配
public class MySpringSecurityConfig extends WebSecurityConfigurerAdapter {  // 启用自定义配置过滤器链

    // springSecurity是基于过滤器（递归调用链进行）连来完成认证授权的 核心过滤器 FilterChainProxy 核心容器 SecurityContextHolder 类似session
    // 以下的属性 依赖注入按顺序进行注入
    // 自定义的拦截器请求之前的拦截器，用来判断 本次请求的 token
    @Autowired
    @Qualifier("myOncePerRequestFilter")
    private MyOncePerRequestFilter oncePerRequestFilter;
    @Autowired
    @Qualifier("myDynamicPermission")
    private MyDynamicPermission dynamicPermission;
    // 进入自定义的 用户名和密码过滤器，为什么使用依赖注入而是使用 @Bean 注解，因为
    // 使用自定义的用户名和密码过滤器的时候需要 同时设置 自定义的 登录成功和失败的过滤器！！！
    // 进入 自定义的 userDetailsService 类来完成二次判断，并且将获取的数据封装到 UserDetails 实体类中
    // 并且响应给 成的处理器
    @Autowired
    @Qualifier("myUserDetailsServiceImpl")
    private UserDetailsService userDetailsService;
    //自定义登录成功过滤器
    @Autowired
    @Qualifier( value = "myAuthenticationSuccessHandler")
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;
    // 自定义登录失败的过滤器
    @Autowired
    @Qualifier( value = "myAuthenticationFailureHandler")
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    // 成功和失败过滤器 只会走一个！！！
    // 自定义的权限不足的 过滤器
    @Autowired
    @Qualifier("myAccessDeniedHandler")
    private AccessDeniedHandler accessDeniedHandler;
    // 自定义的 身份验证 不足的过滤器
    @Autowired
    @Qualifier("myAuthenticationEntryPoint")
    private AuthenticationEntryPoint authenticationEntryPoint;
    // 自定义退出过滤器
    @Autowired
    @Qualifier("myLogoutHandler")
    private LogoutHandler logoutHandler;
    // 自定义退出成功过滤器
    @Autowired
    @Qualifier("myLogoutSuccessHandler")
    private LogoutSuccessHandler logoutSuccessHandler;
    // 自定义密码加密和匹配工具类
    @Autowired
    BCryptPasswordEncoderUtil bCryptPasswordEncoderUtil;
    /**
     * 从容器中取出 AuthenticationManagerBuilder，执行方法里面的逻辑之后，放回容器
     *
     * @param authenticationManagerBuilder
     * @throws Exception
     */
    @Autowired
    public void configureAuthentication(AuthenticationManagerBuilder authenticationManagerBuilder) throws Exception {
        authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoderUtil);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {

        //第1步：解决跨域问题。cors 预检请求放行,让Spring security 放行所有preflight request（cors 预检请求）
        http.authorizeRequests().requestMatchers( CorsUtils::isPreFlightRequest ).permitAll();

        //第2步：让Security永远不会创建HttpSession，它不会使用HttpSession来获取SecurityContext
        http.csrf().disable().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and().headers().cacheControl();

        //第3步：请求权限配置
        //放行注册API请求，其它任何请求都必须经过身份验证.
        http.authorizeRequests()
                .antMatchers(HttpMethod.POST, "/user/register"  , "/captcha/getCaptcha").permitAll()
                //ROLE_ADMIN可以操作任何事情
                //.antMatchers("/**").hasRole("ADMIN")
                //同等上一行代码
                //.antMatchers("/**").hasAuthority("ROLE_ADMIN")
                /*
                 由于使用动态资源配置，以上代码在数据库中配置如下：
                 在sys_backend_api_table中添加一条记录
                 backend_api_id=1，
                 backend_api_name = 所有API，
                 backend_api_url=/**,
                 backend_api_method=GET,POST,PUT,DELETE
                 */

                //动态加载资源 ， 每次请求都会进入该方法来进行判断用户 是否具有权限来执行该操作！！！
                .anyRequest().access("@myDynamicPermission.checkAuthority(request,authentication)");

        //第1步：登录,因为使用前端发送JSON方式进行登录，所以登录模式不设置也是可以的。
        http.formLogin();

        // 第2步，添加自定义的 拦截器 OncePerRequestFilter,在进入自定义的用户名和密码过滤器之前进行拦截
        http.addFilterBefore(oncePerRequestFilter, UsernamePasswordAuthenticationFilter.class);

        //第3步：拦截账号、密码。覆盖 UsernamePasswordAuthenticationFilter过滤器
        http.addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);

        // 进入自定义过滤器判断之后，再进入 自定义的UserDetailsService 来进行二次判断

        // 如果用户请求 出现异常来进行处理
        //第6步：处理异常情况：认证失败和权限不足
        http.exceptionHandling().authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler);

        // 第5步，自定义退出登录的过滤器
        http.logout().addLogoutHandler(logoutHandler).logoutSuccessHandler(logoutSuccessHandler);
    }

    /**
     * 手动注册账号、密码拦截器
     *
     * @return
     * @throws Exception
     */
    @Bean
    MyUsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {
        MyUsernamePasswordAuthenticationFilter filter = new MyUsernamePasswordAuthenticationFilter();
        //修改过滤器

        //成功后处理
        filter.setAuthenticationSuccessHandler(myAuthenticationSuccessHandler);

        //失败后处理
        filter.setAuthenticationFailureHandler(myAuthenticationFailureHandler);

        filter.setAuthenticationManager(authenticationManagerBean());
        return filter;
    }


}
